useCallback 一直是 React hooks 中搞不太懂的地方,只知道是拿來做效能優化。直到看到《React 思維進化》這本書後,發現 useCallback 做效能優化的方式跟我原來想的完全不一樣,而且有蠻多容易搞混的點,那以下開始正文~
useCallback
是 React 的一個 Hook,用來快取(cache)函式。
語法:
const cachedFn = useCallback(fn, dependencies)
fn
:要快取的函式。這個函式通常依賴於 props 或 state,原因以下概念部分會再描述得更詳細。dependencies
:是一個陣列。(與 useEffect 看起來很像,但這邊是必填的。)fn
函式與 dependencies
快取起來。dependencies
:
dependencies
,然後回傳本次渲染時所產生的函式。為什麼 useCallback 要對函式進行快取?
既然函式會還是被創建,並不會減少「不必要的函式產生」,那是什麼原因造成 useCallback 可以拿來做效能優化呢?建議的使用情境是「如果函式會用到 props 或 state ,但又會被 useEffect 使用的情況。」,可以透過以下範例來思考。
假設有一個函式 fetchData()
來根據查詢字串 query
從 API 抓取資料。如果每次重新渲染都創建一個新的 fetchData()
,useEffect
就會一直以為函式變了,導致不必要的 API 請求。
優化前:每次元件重新渲染都會重新創建 fetchData()
,useEffect 也都會判定依賴更新。
import { useEffect } from 'react';
function Child({ query }) {
const fetchData = async () => {
const response = await fetch(`https://foo.com/api/search?query=${query}`);
// ...
};
useEffect(() => {
fetchData(); // 每次渲染都會重新執行
}, [fetchData]); // 因為 fetchData 是新的,useEffect 每次都會執行
// ...
}
優化後:使用 useCallback
記住 fetchData
。
import { useCallback, useEffect } from 'react';
function Child({ query }) {
const fetchData = useCallback(async () => {
const response = await fetch(`https://foo.com/api/search?query=${query}`);
// ...
}, [query]); // 只有當 query 改變時才會回傳本次創建的 fetchData()
useEffect(() => {
fetchData(); // 只在 fetchData() 改變時重新執行
}, [fetchData]); // 因為 fetchData 是記住的,不會每次渲染都執行
// ...
}
query
的值與前一次渲染時的版本相同時,useCallback
就會回傳前一次渲染時所產生的 fetchData()
,然後當 fetchData()
有變化時,useEffect 才會重新執行。所以是如何達到效能優化?
fetchData()
不會在每次元件渲染時都被重新創建而導致 useEffect 的依賴判斷失效,使用 useCallback 後能正確反應 query → fetchData → useEffect 的變化,進而達到效能優化的效果。